/// Copyright (c) Microsoft Corporation.  All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Xml;
using System.Xml.XPath;
using System.Diagnostics;
using System.IO;
using System.Collections.ObjectModel;

namespace Microsoft.VSPowerToys.Common.TaskManager.ContentInstaller
{
    /// <summary>
    /// Base class for VSI Content types. This class defines methods to read XML structures
    /// and also copy files using CopyConflictDialog if necessary.
    /// </summary>
    public abstract class BaseVSContent
    {

        #region Properties

        private List<string> fileNames = new List<string>();

        /// <summary>
        /// Files related to this content package
        /// </summary>
        public ReadOnlyCollection<string> FileNames
        {
            get { return new ReadOnlyCollection<string>(fileNames); }
        }

        private string displayName;

        /// <summary>
        /// Display name of the content
        /// </summary>
        public string DisplayName
        {
            get { return displayName; }
        }

        private string description;

        /// <summary>
        /// Description of the content
        /// </summary>
        public string Description
        {
            get { return description; }
        }

        private string contentType;

        /// <summary>
        /// Type of the content
        /// </summary>
        public string ContentType
        {
            get { return contentType; }
        }

        private string version;

        /// <summary>
        /// Version number
        /// </summary>
        public string Version
        {
            get { return version; }
        }

        private Dictionary<string, string> attributes = new Dictionary<string, string>();
        
        /// <summary>
        /// Attributes related to this content
        /// </summary>
        public Dictionary<string, string> Attributes
        {
            get { return attributes; }
        }

        private string basePath;
        
        /// <summary>
        /// Base path for this package. All file names will be prefixed with this base
        /// path
        /// </summary>
        public string BasePath
        {
            get { return basePath; }
            set { this.basePath = value; }
        }
	
        #endregion

        #region Instance Methods

        /// <summary>
        /// This method should verify all attributes required for the specific content type
        /// is present.
        /// </summary>
        /// <returns>false if verification fails, true otherwise</returns>
        protected abstract bool VerifyContent();

        /// <summary>
        /// This method should install VSI content to its default location (silent install)
        /// </summary>
        public abstract void InstallContent();

        /// <summary>
        /// Tries to copy the specified content file in to the install directory.
        /// Shows the overwrite dialog if necessary
        /// </summary>
        /// <param name="contentFileName">Name of the content file (relative path to base path)</param>
        /// <param name="installPath">Full path to the installation directory</param>
        /// <returns>true if successful, false otherwise</returns>
        protected bool CopyContentFile(string contentFileName, string installPath)
        {
            string sourcePath = Path.Combine(this.BasePath, contentFileName);
            if (File.Exists(sourcePath))
            {
                string targetPath = Path.Combine(installPath, contentFileName);
                if (File.Exists(targetPath))
                {
                    CopyConflictDialog.ConflictResolution resolution = CopyConflictDialog.SavedSetting;
                    if (CopyConflictDialog.SavedSetting == CopyConflictDialog.ConflictResolution.None)
                    {
                        CopyConflictDialog dialog = new CopyConflictDialog(targetPath);
                        dialog.ShowDialog();
                        resolution = dialog.SelectedOption;
                    }
                    switch (resolution)
                    {
                        case CopyConflictDialog.ConflictResolution.None:
                        case CopyConflictDialog.ConflictResolution.Skip:
                            return false;
                        case CopyConflictDialog.ConflictResolution.SaveNew:
                            targetPath = CopyConflictDialog.GetNextAvailableFileName(targetPath);
                            break;
                        default:
                            break;
                    }
                }
                try
                {
                    File.Copy(sourcePath, targetPath, true);
                    return true;
                }
                catch (IOException ex) 
                {
                    throw new VsiException(ex.Message, this);
                }
            }
            else
            {
                throw new VsiException(Microsoft.VSPowerToys.Common.TaskManager.ExceptionStrings.SourceFileDoesNotExist, this);
            }
        }
        #endregion

        #region Static Methods 

        /// <summary>
        /// Creates an appropiate type of BaseVSContent from given contentNode
        /// </summary>
        /// <param name="contentNode">XmlNode to process (must be a Content node)</param>
        /// <returns>a BaseVSContent instance that matches the type of the content</returns>
        internal static BaseVSContent CreateFromXmlNode(XmlNode contentNode)
        {
            if (contentNode == null) throw new ArgumentNullException("contentNode");
            try {
                BaseVSContent content;
                XPathNavigator navigator = contentNode.CreateNavigator();
                string contentType = GetChildValue(navigator, "FileContentType", true);
                switch (contentType.ToLower(System.Globalization.CultureInfo.CurrentCulture)) 
                {
                    case "code snippet":
                        content = new SnippetContent();
                        break;
                    case "vstemplate":
                        content = new TemplateContent();
                        break;
                    case "toolbox control":
                        content = new ToolboxContent();
                        break;
                    case "addin":
                        content = new AddinContent();
                        break;
                    default:
                        Trace.WriteLine("Unknown file content type");
                        return null;
                }
                content.contentType = contentType;
                content.description = GetChildValue(navigator, "Description", false);
                content.version = GetChildValue(navigator, "ContentVersion", true);
                if (!content.version.Equals("1.0"))
                {
                    Trace.WriteLine("Invalid version information");
                    return null;
                }
                content.displayName = GetChildValue(navigator, "DisplayName", true);
                // Read attributes and filenames
                foreach (XmlNode node in contentNode.ChildNodes)
                {
                    if (node.Name.Equals("Attributes")) {
                        foreach (XmlNode attributeNode in node.ChildNodes)
                        {
                            if (!attributeNode.Name.Equals("Attribute"))
                            {
                                Trace.WriteLine("Unknown node encountered in Attributes section");
                                return null;
                            }
                            if (attributeNode.Attributes != null && attributeNode.Attributes["name"] != null &&
                                attributeNode.Attributes["value"] != null)
                            {
                                content.attributes.Add(
                                    attributeNode.Attributes["name"].Value, 
                                    attributeNode.Attributes["value"].Value);
                            }
                            else
                            {
                                Trace.WriteLine("Invalid attributes node encountered.");
                                return null;
                            }
                        }
                    }
                    else if (node.Name.Equals("FileName"))
                    {
                        content.fileNames.Add(node.InnerText);
                    }
                }
                if (content.fileNames.Count == 0)
                {
                    Trace.WriteLine("No filename entry could be found in content: " + content.DisplayName);
                    return null;
                }
                if (content.VerifyContent())
                {
                    return content;
                }
                else
                {
                    Trace.WriteLine("Content information could not be verified for " + content.DisplayName);
                    return null;
                }
            } 
            catch (ArgumentException) 
            {
                return null;
            }
        }

        private static string GetChildValue(XPathNavigator navigator, string childName, bool failIfNoteFound)
        {
            XPathNodeIterator iterator = navigator.SelectChildren(childName, navigator.NamespaceURI);
            if (iterator.Count == 0)
            {
                if (failIfNoteFound)
                {
                    Trace.WriteLine("Invalid number of " + childName + " elements");
                    throw new ArgumentOutOfRangeException("childName");
                }
                else
                {
                    return String.Empty;
                }
            }
            else
            {
                iterator.MoveNext();
            }
            return iterator.Current.Value;
        }

        protected static bool CheckValidity(string valueToCheck, string[] validValues) 
        {
            if (validValues == null)
            {
                return true;
            }
            foreach (string value in validValues)
            {
                if (value.Equals(valueToCheck)) return true;
            }
            return false;
        }
        #endregion
    }

    /// <summary>
    /// This class contains functionality required for using Code Snippet content types
    /// </summary>
    public class SnippetContent : BaseVSContent
    {

        private string language;
        
        /// <summary>
        /// Gets the language code snippet designed for
        /// </summary>
        public string Language
        {
            get { return language; }
        }
	
        /// <see cref="BaseVSContent.VerifyContent"/>
        protected override bool VerifyContent()
        {
            if (!this.ContentType.Equals("Code Snippet"))
            {
                return false;
            }
            if (!this.Attributes.ContainsKey("lang")) return false;
            this.language = this.Attributes["lang"].ToLower(System.Globalization.CultureInfo.CurrentCulture);
            String[] validLanguages = new String[] { "csharp", "jsharp", "vb", "xml" };
            return CheckValidity(this.Language, validLanguages);
        }

        /// <summary>
        /// Installs snippet files to My Code Snippets directory for the relevant language.
        /// </summary>
        public override void InstallContent()
        {
            string snippetPath = "Code Snippets";
            string languagePath = null;
            switch (this.Language)
            {
                case "csharp":
                    languagePath = @"Visual C#\My Code Snippets";
                    break;
                case "jsharp":
                    languagePath = @"Visual J#\My Code Snippets";
                    break;
                case "vb":
                    languagePath = @"Visual Basic\My Code Snippets";
                    break;
                case "xml":
                    languagePath = @"XML\My XML Snippets";
                    break;
                default:
                    throw new VsiException(Microsoft.VSPowerToys.Common.TaskManager.ExceptionStrings.UnknownSnippetLanguage, this);
            }
            snippetPath = Path.Combine(snippetPath, languagePath);
            List<string> installedPaths = new List<string>();
            foreach (ApplicationHostData host in ApplicationHostData.GetHostData(this.ContentType, this.Version))
            {
                string installPath = Path.Combine(host.UserDataFolder, snippetPath);
                if (!installedPaths.Contains(installPath))
                {
                    installedPaths.Add(installPath);
                    if (Directory.Exists(installPath))
                    {
                        foreach (string fileName in this.FileNames)
                        {
                            this.CopyContentFile(fileName, installPath);
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    /// This class contains functionality required for using Template content types
    /// </summary>
    public class TemplateContent : BaseVSContent
    {

        /// <summary>
        /// Possible template types
        /// </summary>
        public enum TemplateType 
        {
            Project, Item
        }

        private string projectType;

        /// <summary>
        /// Project type that template is designed for
        /// </summary>
        public string ProjectType
        {
            get { return projectType; }
        }

        private string projectSubtype = String.Empty;

        /// <summary>
        /// Template's sub type (it will be placed under this directory name)
        /// </summary>
        public string ProjectSubtype
        {
            get { return projectSubtype; }
        }

        private TemplateType type;

        /// <summary>
        /// Type of the template
        /// </summary>
        public TemplateType Type
        {
            get { return this.type; }
        }

        protected override bool VerifyContent()
        {
            if (!this.ContentType.Equals("VSTemplate"))
            {
                return false;
            }
            if (!this.Attributes.ContainsKey("ProjectType")) return false;
            if (!this.Attributes.ContainsKey("TemplateType")) return false;

            this.projectType = this.Attributes["ProjectType"];
            if (this.Attributes.ContainsKey("ProjectSubType"))
            {
                this.projectSubtype = this.Attributes["ProjectSubType"];
            }
            switch (this.Attributes["TemplateType"].ToLower(System.Globalization.CultureInfo.CurrentCulture)) 
            {
                case "project":
                    this.type = TemplateType.Project;
                    break;
                case "item":
                    this.type = TemplateType.Item;
                    break;
                default:
                    Debug.WriteLine("Unknown template type at " + this.DisplayName);
                    return false;
            }
            return true;
        }

        /// <see cref="BaseVSContent.InstallContent"/>
        public override void InstallContent()
        {
            string templatePath = "Templates";
            switch (this.type) 
            {
                case TemplateType.Project:
                    templatePath = Path.Combine(templatePath, "ProjectTemplates");
                    break;
                case TemplateType.Item:
                    templatePath = Path.Combine(templatePath, "ItemTemplates");
                    break;
            }
            templatePath = Path.Combine(templatePath, this.ProjectType);
            if (!String.IsNullOrEmpty(this.ProjectSubtype))
            {
                templatePath = Path.Combine(templatePath, this.ProjectSubtype);
            }
            List<string> installedPaths = new List<string>();
            foreach (ApplicationHostData host in ApplicationHostData.GetHostData(this.ContentType, this.Version))
            {
                string installPath = Path.Combine(host.UserDataFolder, templatePath);
                if (!installedPaths.Contains(installPath))
                {
                    installedPaths.Add(installPath);
                    if (!Directory.Exists(installPath))
                        Directory.CreateDirectory(installPath);
                    foreach (string fileName in this.FileNames)
                    {
                        this.CopyContentFile(fileName, installPath);
                    }
                }
            }
        }
    }

    /// <summary>
    /// This class contains functionality required for using Template content types
    /// </summary>
    public class AddinContent : BaseVSContent
    {

        protected override bool VerifyContent()
        {
            return this.ContentType.Equals("Addin");
        }

        public override void InstallContent()
        {
            string addinPath = "Addins";
            List<string> installedPaths = new List<string>();
            foreach (ApplicationHostData host in ApplicationHostData.GetHostData(this.ContentType, this.Version))
            {
                string installPath = Path.Combine(host.UserDataFolder, addinPath);
                if (!installedPaths.Contains(installPath))
                {
                    installedPaths.Add(installPath);
                    foreach (string fileName in this.FileNames)
                    {
                        this.CopyContentFile(fileName, installPath);
                    }
                }
            }
        }
    }

    /// <summary>
    /// This class contains functionality required for using Template content types
    /// </summary>
    public class ToolboxContent : BaseVSContent
    {

        protected override bool VerifyContent()
        {
            return this.ContentType.Equals("Toolbox Control");
        }

        public override void InstallContent()
        {
            string contentPath = "Controls";
            List<string> installedPaths = new List<string>();
            foreach (ApplicationHostData host in ApplicationHostData.GetHostData(this.ContentType, this.Version))
            {
                string installPath = Path.Combine(host.UserDataFolder, contentPath);
                if (!installedPaths.Contains(installPath))
                {
                    installedPaths.Add(installPath);
                    foreach (string fileName in this.FileNames)
                    {
                        if (!this.CopyContentFile(fileName, installPath))
                        {
                            throw new VsiException(Microsoft.VSPowerToys.Common.TaskManager.ExceptionStrings.FileCopyFailed, this);
                        }
                    }
                }
                if (!host.ExecuteCommand("Tools.InstallCommunityControls"))
                {
                    throw new VsiException(Microsoft.VSPowerToys.Common.TaskManager.ExceptionStrings.InstallCommandFailed, this);
                }
            }
        }
    }
}
